package myEngine;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import java.util.regex.Pattern;

import movgen.Move2;
import movgen.Util;
import search.Search2;
import search.TimeControl;
import search.TranspositionTable;
import state.GameState;
import book.Book2;

public class Engine2 {
	    public static final 		    String version = "1.3";
	    private static 					GameState state;
	    private static BufferedWriter   writer;
	  
		private static boolean outOfBook=false;
		private static Book2 book;
		//private static long saveHash;
		//private static int saveFlags;
		private static List<Integer> legalMoves;
		public static boolean forceMode=true;
		private static final int searchDepth=9;
		public static int movesMade=0;
		public static int numBookMovesMade=0;
		public static final int NONE=-1;
		public static int sideToMove=NONE, computerSide=NONE;    // 0 is WHITE, 1 is BLACK
		public static boolean myTurn=false;
		
		/*  PONDERING ALGORITHM not yet implemented
		 * 
		 * 1) Do normal search 
  2) note the 2nd move in the PV as the expected opponent move. 
  3) make the expected ponder move on its internal board and starts a ponder search for unlimited time until opponent moves. 
  
  4) The opponent makes a move: 
      4a) Ponder miss: 
      		performs a takeback 
      		 makes the opponents move
      		 restarts the search as normal. 
      		 
      4b) Ponder Hit: 
            4b1) Not enough search (opponent quick move): Continue searching until satisfied. 
            4b2) Enough time: Make move. 
              
		 */
		static {
			 book = new Book2("tiny.book");
		}
		
		public static void main(String args[]) throws java.io.IOException
		{
		     /*   try {
		            String filename = "/Applications/Xboard/chesslog.txt";
		            File logFile = new File(filename);
		            writer = new BufferedWriter(new FileWriter(logFile));
		            writer.write("Starting Game\n");
		            writer.flush();
		        } 
		        catch (Exception e) 
		        {
		            e.printStackTrace();
		        } */
		        
			 Runtime.getRuntime().addShutdownHook(new Thread()
		        {
		            @Override
		            public void run()
		            {
		                System.out.println("Shutdown hook ran!");
		              /*  try {
		                	writer.write("Finishing game\n");
							writer.close();
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}*/
		            }
		        });
			 
			BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) );
	        write("Welcome to Javalin Chess Engine " + version);
	        write("feature sigterm=0");
	        write("feature sigint=0");
	        write("Type 'new' to start a new game, or 'quit' to end.");
	        write("Then input a move ( Ex. 'e2e4' ) .");
	      
	        setUsingTimer(false);  // we don't use timing for the console; for xboard we do
	        
			String input;
			do
			{
				try
				{
					input = br.readLine().trim();
	                if(input.equals("quit"))
	                {
	                    break;
	                }
	                else
	                {
	                    processCommand(input);
	                }
				}
	            catch(Exception e)
	            {
	                e.printStackTrace();
	            }
			} while (true);
	     
		}
		
		private static void processCommand(String input) {
			if( input.equals("xboard") ) {
				setUsingTimer(true);
			}
			else
			if( input.equals("protover 2") )
			{
				write("feature myname=\"Engine2 " + Engine2.version + 
						"\" setboard=1 analyze=0 variants=\"normal\" colors=0 debug=1 done=1");
			}
			else if( input.startsWith("setboard") )
		    {
		        	String FEN = input.substring(9); // After "setboard "
		            state = new GameState(FEN);
		            setState(state);
		            sideToMove=state.color;
		           
		    }
			else
		    if (input.equals("new")) { 
		            newGame();
		    }
		    else if( input.equals("force") )
			{
			        forceMode=true;
			        computerSide=NONE;
			            
			}
			else if( input.equals("go") )
			{
				if (sideToMove==NONE) {
					sideToMove=GameState.WHITE;
				}
				forceMode=false;
				computerSide=sideToMove;
				myTurn=true;
		        findAndApplyMove();
		            
			}
			/*else if( input.equals("undo") )
			{
				undo();
	          
			}
			else if( input.equals("remove") )
			{
				undo();
				undo();
			}*/
			else if( input.startsWith("level") )
		    {
		        	  TimeControl.setTimeLevel(input);
		    }
		    else if ( input.startsWith("time") )
		    {
		        	  try
		              {
		                  // the time X commands sends X centiseconds
		                  long time = Long.parseLong(input.substring(5)) * 10;
		                  Search2.timeLeft=time;
		                  
		              }
		              catch(NumberFormatException e)
		              {
		              }
		     }
		     else if ( input.startsWith("otim") )
		     {
		        	  try
		              {
		                  // the time X commands sends X centiseconds
		                  long otim = Long.parseLong(input.substring(5)) * 10;
		                  Search2.opponentTimeLeft=otim;
		              }
		              catch(NumberFormatException e)
		              {
		              }
		     }
		     else 
					if (coordinateMovePattern.matcher(input).matches()) {
						if (sideToMove==NONE) {
							//we have received a move for the first time, so the computer must be playing black
							sideToMove=GameState.BLACK;
							computerSide=GameState.BLACK;
						}
						applyMove(input);
						if (sideToMove == computerSide) myTurn=true;
						// do the normal search
						if (myTurn) findAndApplyMove();
					}
		        }

		// The following two regular expressions come from the Frittle chess engine by Rohan Padyhe
		//http://frittle.sourceforge.net/
		
	    /**
	     * A regular expression for a move in coordinate notation (eg. e2e4 or g7g8q)
	     * Groups:
	     * 1 => file of source square
	     * 2 => rank of source square
	     * 3 => file of target square
	     * 4 => rank of target square
	     * 5 => character code of promotional piece, if any
	     */
	    protected static Pattern coordinateMovePattern =
	            Pattern.compile("([a-h][1-8])*([a-h][1-8])([qrnbQRNB])?");

	    /**
	     * A regular expression for specifying time formats
	     * Groups:
	     * 1 => Moves per session
	     * 2 => Base time minutes
	     * 3 => Base time seconds, if any
	     * 4 => Increment time in seconds
	     */
	    protected static Pattern timeControlsPattern =
	            Pattern.compile("(\\d+) (\\d+)(?:\\:(\\d{2}))? (\\d+)");
	    
	    public static GameState getState() {
			return state;
		}
		
		
		public static void newGame() {
			/*
			 *   Reset the board to the standard chess starting position.
			 *   Set White on move.
			 *   Leave force mode and set the engine to play Black.
			 *   Associate the engine's clock with Black and the opponent's clock with White.
			 *   Reset clocks and time controls to the start of a new game. 
			 *   Do not ponder on this move, even if pondering is on. 
			 *   Remove any search depth limit previously set by the sd command. 
			 */
			forceMode=false;
			GameState.currentPly=0;
			
			state = new GameState("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
			//GameState.stateHistory[GameState.currentPly++]=state.hash;
			GameState.resetMoveHistory();
			legalMoves=Util.getLegalMoveList(state);
			outOfBook=false;
			movesMade=0;
			numBookMovesMade=0;
			sideToMove=GameState.WHITE;
			computerSide=GameState.BLACK;
		}
		
		public static int  applyMove(String moveStr)
		//make the move on our chessboard.
		// note that this could be happening right after we did a search OR
		// in response to getting a move from the opponent
		{
			    if (state==null) return 0;
			    if (moveStr==null) {
			    	write("Empty move in applyMove");
			    	return 0;
			    } 
			    if (legalMoves.size() < 1) {
			    	write("Game over; no legal moves");
			    	return 0;
			    }
				int move = validate(moveStr);
				if (move == 0) {
					write("Illegal move " + moveStr);
					write( state.toString());
	            	write( getLegalMovesToString());
	            	return 0;
				}
			
				//saveHash=state.hash;
				//saveFlags=state.flags;
					
				state.make(move);
				legalMoves=Util.getLegalMoveList(state);
				if (TranspositionTable.useTT) {
					TranspositionTable.hashStore(state.hash, TranspositionTable.GAME_MOVE_DEPTH, 0,0, 0, 0); 
				}
				sideToMove = toggle(sideToMove);
				return move;
		}
		
		private static int toggle(int sideToMove) {
			return ( 1 - sideToMove);
		}
		
		public static void findAndApplyMove()
		//this is called when the GUI sends the opponents move
		// so we need to do a search
		{
			    if (state==null) return;
			    GameState state2= new GameState(state);
			    String bestmove;
			    
			    if (!outOfBook) {
			    	bestmove = book.getMove(state2);
			    	if (bestmove==null) {
			    		outOfBook=true;
			    		
			    		bestmove = Search2.calcBestMove(state2, searchDepth);
			    	}
			    	else
			    		numBookMovesMade++;
			    }
			    else
			    	
			    	bestmove = Search2.calcBestMove(state2, searchDepth);
				applyMove(bestmove);
				write("move " + bestmove);
				myTurn=false;
		}
		

		public static void setState(GameState state2) {
			if (state2==null) return;
			state=state2;
		}
		public static String getLegalMovesToString() {
			if (legalMoves.isEmpty()) return " No legal moves ";
			
	    	StringBuffer sb = new StringBuffer();
	    	for (int i=0; i < legalMoves.size(); i++) {
	    		
	    		int move = legalMoves.get(i);
	    		sb.append(Move2.toString(move));
	    		sb.append(" ,");
	    	}
	    	return sb.toString();
	    }
		private static int  validate(String moveStr) {
			for (int i=0; i < legalMoves.size(); i++) {
				int legalMove = legalMoves.get(i);
				String moveStr2 = Move2.toString(legalMove);
				if (moveStr2.equalsIgnoreCase(moveStr)) {
					return legalMove;
				}
			}
			return 0;
		}

		public static void setUsingTimer(boolean b) {
			Search2.setUsingTime(b);
			
		}
/*
		public static void undo() {
			int move = GameState.getLastMove();
			state.unmake(move, saveFlags,saveHash);
			legalMoves=Util.getLegalMoveList(state);
			
		}*/
		public static void write(String message)
		{
			System.out.println(message);
			System.out.flush();
		}
		public static void debug(String message)
		{
			try {
				writer.write(message+"\n");
				writer.flush();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
	}


